[小ネタ]Terraformでホストゾーンが分かれるSANs証明書をACMで発行する
こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。
いきなりですが質問です。
CloudFrontでは「example.com」、「example.jp」等の複数の代替ドメインを設定できます。
しかし、証明書は1ディストリビューションあたり1つまで設定できます。
「dev.example.com」、「image.example.com」の組み合わせのようなトップレベルドメインが一緒の場合は、「*.example.com」のようなワイルドカード証明書が使えます。
ですが、「example.com」、「example.jp」等のトップレベルドメインが重複しない場合どのように実装しますか?
答えは、「サブジェクト代替名を用いた証明書を使用する」です。
懺悔します。私、たかくにはACMがサブジェクト代替名で設計されていることに気がつかず、2つ以上の証明書を作成してCloudFrontに組み込もうとしてしまいました。(もちろんエラーで詰まりました。)
今回は、(勝手に)懺悔ブログです。(会社で懺悔を強要することはないのでご安心を)
Route53ホストゾーンが異なるパターンをTerraformで書いたことなかったためブログにしようと思いました。
前提
今回は、「example.com」、「example.jp」のような別々のホストゾーンに対して、SANs証明書をACMで発行することを目的としています。
同一ホストゾーンでSANs証明書をACMで発行するパターンは対象外とします。
本題
本題に入りますが、「example.com」、「example.jp」のようなトップレベルドメインが重複しない場合、Route53ホストゾーンが別々になります。
したがって、以下の記載されているコードだとエラーを返します。(本来、「example.jp」に対して設定するレコードおよびレコードの検証分が、ホストゾーン「example.com」に対して行われているため)
resource "aws_acm_certificate" "example" {
domain_name = "example.com"
validation_method = "DNS"
subject_alternative_names = ["example.jp"]
}
data "aws_route53_zone" "example" {
name = "example.com"
private_zone = false
}
resource "aws_route53_record" "example" {
for_each = {
for dvo in aws_acm_certificate.example.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.example.zone_id
}
resource "aws_acm_certificate_validation" "example" {
certificate_arn = aws_acm_certificate.example.arn
validation_record_fqdns = [for record in aws_route53_record.example : record.fqdn]
}
別々のホストゾーンで発行する場合、公式ドキュメントのサンプルコードでは、以下の処理が行われているように見受けられます。
- ホストゾーンごとに
data.aws_route53_zone
ブロックを定義して参照する - 三項演算子を用いてホストゾーンの条件分岐を行う
resource "aws_acm_certificate" "example" {
domain_name = "example.com"
subject_alternative_names = ["www.example.com", "example.org"]
validation_method = "DNS"
}
data "aws_route53_zone" "example_com" {
name = "example.com"
private_zone = false
}
data "aws_route53_zone" "example_org" {
name = "example.org"
private_zone = false
}
resource "aws_route53_record" "example" {
for_each = {
for dvo in aws_acm_certificate.example.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
zone_id = dvo.domain_name == "example.org" ? data.aws_route53_zone.example_org.zone_id : data.aws_route53_zone.example_com.zone_id
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = each.value.zone_id
}
resource "aws_acm_certificate_validation" "example" {
certificate_arn = aws_acm_certificate.example.arn
validation_record_fqdns = [for record in aws_route53_record.example : record.fqdn]
}
resource "aws_lb_listener" "example" {
# ... other configuration ...
certificate_arn = aws_acm_certificate_validation.example.certificate_arn
}
Resource: aws_acm_certificate_validationより引用
上記のパターンで実装するのも良いと思いますが、「Terraformの三項演算子は3パターン以上に対応できない」、「ACMでは1つの証明書あたり最大100ドメインまで設定可能」の懸念事項があるため対応できるようにカイゼンしてみます。
作ってみたコード
module
で作成しましたが以下のコードが完成したコードになります。こだわりは、count
を使わずfor_each
で作成してみました。
aws_route53_record
のzone_id
の受け渡しもイイ感じにかけたかなと思います。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.1.0"
}
}
}
resource "aws_acm_certificate" "cert" {
domain_name = var.domain_name
subject_alternative_names = var.subject_domain_names
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
data "aws_route53_zone" "cert" {
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {}
}
name = each.key
private_zone = false
}
resource "aws_route53_record" "cert" {
for_each = {
for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
type = each.value.type
ttl = 60
zone_id = data.aws_route53_zone.cert[each.key].zone_id
}
resource "aws_acm_certificate_validation" "cert" {
certificate_arn = aws_acm_certificate.cert.arn
validation_record_fqdns = [for record in aws_route53_record.cert : record.fqdn]
}
variable "domain_name" {
type = string
default = ""
}
variable "subject_domain_names" {
type = list(string)
default = []
}
output "cert_arn" {
value = aws_acm_certificate.cert.arn
}
使ってみた
使い方としては、シンプルにmodule
を呼び出します。subject_domain_names
に追加したい「サブジェクト代替名」のドメインを定義するような使い方です。
module "san_acm_cf" {
source = "/../modules/san_acm"
domain_name = "example.com"
subject_domain_names = [
"dev.takakuni.link"
]
}
無事、別々のホストゾーンに対応したSANs証明書を発行できました。
終わりに
以上、「Terraformでホストゾーンが分かれるSANs証明書をACMで発行する」でした。
小ネタすぎて、どのニーズに刺さるのか怪しいですが、この記事がどなたかの参考になれば幸いです。
以上、AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!